package dipl.algorithm.math.primitive.op;

import dipl.algorithm.math.primitive.Point2d;
import dipl.algorithm.math.utility.FPMath;
import dipl.algorithm.math.primitive.Side;
import dipl.algorithm.math.primitive.Vector2d;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;

/**
 * Class provides operations on vectors
 */
public class Vector2dOps {

	//
	// PUBLIC METHODS
	//

	/**
	 * Calculates (smaller) angle between both given vectors.
	 * If length of at least one of the given vectors is zero, zero is returned
	 * @param fpMath
	 * @param v1
	 * @param v2
	 * @return
	 */
	public static Apfloat Angle( FPMath fpMath, Vector2d v1, Vector2d v2 ) {
		if( v1.x.compareTo( Apfloat.ZERO ) == 0 && v1.y.compareTo( Apfloat.ZERO ) == 0 )
			return fpMath._0;
		if( v2.x.compareTo( Apfloat.ZERO ) == 0 && v2.y.compareTo( Apfloat.ZERO ) == 0 )
			return fpMath._0;
		Apfloat a = fpMath.m.divide(
									DotProduct( fpMath, v1, v2 ),
									fpMath.m.multiply( v1.Norm( fpMath ), v2.Norm( fpMath ) )
								);
		try {
			return fpMath.m.acos( a );
		}
		catch( Exception e ) {
			return fpMath._0;
		}
	}

	public static Apfloat Area( Vector2d v0, Vector2d v1 ) {
		return ApfloatMath.multiplySubtract( v0.x, v0.y.add( v1.y ),
																				 v0.y, v0.x.add( v1.x ) );
	}

	public static Apfloat Area( Apfloat v0x, Apfloat v0y, Apfloat v1x, Apfloat v1y ) {
		return ApfloatMath.multiplySubtract( v0x, v0y.add( v1y ),
																				 v0y, v0x.add( v1x ) );
	}

	/**
	 * Calculates dot product of given two vectors
	 * @param fpMath
	 * @param v1
	 * @param v2
	 * @return
	 */
	public static Apfloat DotProduct( FPMath fpMath, Vector2d v1, Vector2d v2 ) {
		return fpMath.m.multiplyAdd( v1.x, v2.x, v1.y, v2.y );
	}

	/**
	 * Returns difference vector (q.x - p.x, q.y - p.y)
	 * @param fpMath
	 * @param p
	 * @param q
	 * @return
	 */
	public static Vector2d DifferenceVector( FPMath fpMath, Point2d p, Point2d q ) {
		return new Vector2d( fpMath.m.subtract( q.x, p.x ), fpMath.m.subtract( q.y, p.y ) );
	}

	/**
	 * Calculates difference vector (q.x - p.x, q.y - p.y)
	 * @param fpMath
	 * @param p
	 * @param q
	 * @param result
	 */
	public static void DifferenceVector( FPMath fpMath, Point2d p, Point2d q,/*out*/ Vector2d result ) {
		result.x = fpMath.m.subtract( q.x, p.x );
		result.y = fpMath.m.subtract( q.y, p.y );
	}

	/**
	 * Tests whether or not both vectors make a right turn.
	 * In other words it is tested whether or not v1 is right of v0.
	 * @param fpMath
	 * @param v0
	 * @param v1
	 * @return
	 */
	public static boolean MakeRightTurn( Vector2d v0, Vector2d v1 ) {
		return Area( v0, v1 ).compareTo( Apfloat.ZERO ) < 0;
	}

	/**
	 * For given vector (v.x,v.y) this method calculates vector p=(v.y,-v.x) which is
	 * perpendicular to v.
	 * @param v input
	 * @param p result
	 */
	public static void Perp( Vector2d v, /*out*/ Vector2d p ) {
		p.Set( v.y, v.x.negate() );
	}

	/**
	 * Calculates whether v1 lies to the right, on or to the left of v0.
	 * @param fpMath
	 * @param v0
	 * @param v1
	 * @return
	 */
	public static Side WhichSide( Vector2d v0, Vector2d v1 ) {
		int c = Area( v0, v1 ).compareTo( Apfloat.ZERO );
		if( c < 0 )
			return Side.Right;
		if( c > 0 )
			return Side.Left;
		return Side.Onto;
	}

	/**
	 * Calculates whether (v1x,v1y) lies to the right, on or to the left of (v0x,v0y).
	 * @param fpMath
	 * @param v0x
	 * @param v0y
	 * @param v1x
	 * @param v1y
	 * @return
	 */
	public static Side WhichSide( Apfloat v0x, Apfloat v0y, Apfloat v1x, Apfloat v1y ) {
		int c = Area( v0x, v0y, v1x, v1y ).compareTo( Apfloat.ZERO );
		if( c < 0 )
			return Side.Right;
		if( c > 0 )
			return Side.Left;
		return Side.Onto;
	}
}
